﻿using System;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Noria.Web.MyMSDN.Models;
using Noria.Web.Utility;
using MmContext = Noria.Web.MyMSDN.Models.MmContext;

namespace Noria.Web.MyMSDN
{
    /// <summary>
    /// Summary description for checksum
    /// </summary>
    public class ChecksumHandler : IHttpHandler
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        private static readonly log4net.ILog alog = log4net.LogManager.GetLogger("Noria.Audit");
        private static object _sync = new object();
        private const string KeyCipher = "cipher";

        public void ProcessRequest(HttpContext context)
        {
            // get cipher
            if (context.Request.HttpMethod == WebRequestMethods.Http.Get)
            {
                var cipher = context.Cache[KeyCipher] as Tuple<byte[], byte[]>;
                if (cipher == null)
                {
                    lock (_sync)
                    {
                        if (context.Cache[KeyCipher] == null)
                        {
                            cipher = SimpleRSA.CreateKey();
                            context.Cache[KeyCipher] = cipher;

                            log.DebugFormat("Cipher created, public key={0}, keypair={1}",
                                            BitConverter.ToString(cipher.Item1).Replace("-", ""),
                                            BitConverter.ToString(cipher.Item2).Replace("-", ""));
                        }
                    }
                }

                context.Response.ContentType = "application/octet-stream";
                context.Response.BinaryWrite(cipher.Item1);
                return;
            }

            // we only accept POST method and ignore encoding
            if (context.Request.HttpMethod != WebRequestMethods.Http.Post)
            {
                throw new MethodNotAllowedException();
            }

            // get key
            var key = context.Cache[KeyCipher] as Tuple<byte[], byte[]>;
            if (key == null)
            {
                throw new ServerException("Key has expired.");
            }

            // read raw request body
            var length = context.Request.ContentLength;
            var raw = context.Request.BinaryRead(length);

            // decode by RSA
            var rawText = Encoding.UTF8.GetString(SimpleRSA.Decrypt(raw, key.Item2));
            log.Debug(rawText);

            // p0: sha1
            // p1: ed2k
            // p2: size
            var list = rawText.Split('|');
            if (list.Length != 3)
            {
                throw new ArgumentOutOfRangeException("rawText should has only 3 parts");
            }

            using (var db = new MmContext())
            {
                var sha1 = list[0];
                var ed2k = list[1];
                var size = long.Parse(list[2]);
                var sizeMB = Math.Round(size / (double)(1024 * 1024)).ToString() + " MB";
                var state = 0; //0 - not found, 1 - existing, 2 - new

                var file = db.Files.FirstOrDefault(p => p.Sha1 == sha1 && p.DisplaySize == sizeMB);
                if (file == null)
                {
                    // not found
                }
                else
                {
                    // found
                    if (file.eMule == ed2k)
                    {
                        state = 1;
                    }
                    else
                    {
                        // update
                        if (file.eMule == null)
                        {
                            state = 2;

                            file.eMule = ed2k;
                            file.Size = size;

                            db.Checksum.Add(new Checksum
                            {
                                eMule = ed2k,
                                FileName = "<uploaded>",
                                Sha1 = sha1,
                                Size = size,
                                Initiator = ClientHelper.GetIPAddress(context.Request),
                                SubmittedDate = DateTime.Now
                            });

                            db.SaveChanges();
                            alog.InfoFormat("Update file {0}:{1}, eMule={2}", file.FileId, file.FileName, ed2k);
                        }
                        else
                        {
                            // abnormal
                            state = 3;
                            alog.InfoFormat("Abnormal file {0}:{1}, eMule={2}, ed2k={3}", file.FileId, file.FileName, file.eMule, ed2k);
                        }
                    }
                }

                var payload = "";
                if (state == 0)
                {
                    // p0: 0 - not found, 1 - existing, 2 - new, 3 - abnormal
                    // p1: file name
                    // p2: sha1
                    // p3: size
                    payload = string.Join("|", state.ToString(), "", sha1, size.ToString());
                }
                else
                {
                    payload = string.Join("|", state.ToString(), file.FileName, file.Sha1, file.Size.ToString());
                }

                //var family = db.Families.Include("Files").FirstOrDefault(p => p.FamilyId == familyId);
                //if (family == null)
                //{
                //    throw new ServerException("The request product family doesn't exist.");
                //}


                //var files = family.Files
                //                  .Where(p => p.Enabled && p.Locale == locale)
                //                  .OrderByDescending(p => p.PostedDate).ToArray();
                //var payload = JArray.FromObject(files).ToString(Formatting.None);

                context.Response.ContentType = "text/plain";
                context.Response.Write(payload);
                log.Debug(payload);
            }

        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}